﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace aerowan.pilot_client.Simulator
{
    public class FlightSimulator : INotifyPropertyChanged
    {
        #region Singleton

        public static FlightSimulator Instance { get; private set; }
        static FlightSimulator()
        {
            Instance = new FlightSimulator();
        }

        FlightSimulator()
        {

        }

        #endregion

        #region NotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        /// <summary>
        /// Handle to fsuipc
        /// </summary>
        Fsuipc h_FSUIPC;

        #region FSUIPC Multi Thread Management

        List<Action> _pendingActions = new List<Action>();

        void PendAction(Action act)
        {
            lock (_pendingActions)
            {
                _pendingActions.Add(act);
            }
        }

        Thread _requestThread;

        void EnableInterest()
        {
            _requestThread = new Thread(new ThreadStart(() =>
            {
                while (true)
                {
                    if (_pendingActions.Count == 0)
                    {
                        Thread.Sleep(250);
                        continue;
                    }
                    Action toExecute;
                    lock (_pendingActions)
                    {
                        toExecute = _pendingActions[0];
                        _pendingActions.RemoveAt(0);
                    }
                    toExecute.Invoke();
                }
            }));
            _requestThread.Start();
        }

        #endregion

        /// <summary>
        /// Connect to Flight Simulator using FSUIPC.
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {
            // This method must block until we are done connecting to sim.
            AutoResetEvent are = new AutoResetEvent(false);
            bool result = false;

            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                try
                {
                    // Connect to Sim using FSUIPC
                    h_FSUIPC = new Fsuipc();
                    // Init link
                    h_FSUIPC.FSUIPC_Initialization();

                    int error = 0;
                    if (!h_FSUIPC.FSUIPC_Open(Fsuipc.SIM_ANY, ref error))
                    {
                        MessageBox.Show("FSUIPC_Open<SIM_ANY> Error " + error);
                        result = false;
                        are.Set();
                        return;
                    }

                    EnableInterest();

                    ReadString(Offsets.AIRCRAFT_NAME, 256, acftName =>
                    {
                        if (String.IsNullOrEmpty(acftName))
                        {
                            // No airplane loaded
                            result = false;
                            are.Set();
                            return;
                        }
                        result = true;
                        are.Set();
                    });
                }
                catch (Exception e)
                {
                    MessageBox.Show("FSUIPC Connection Failed: " + e.Message);
                    result = false;
                    are.Set();
                }
            }));

            are.WaitOne();
            return result;
        }

        /// <summary>
        /// Close link to FSUIPC.
        /// Docs says no harm to close twice ...
        /// </summary>
        public void Close()
        {
            if (_requestThread != null) _requestThread.Abort();
            if (h_FSUIPC != null) h_FSUIPC.FSUIPC_Close();
        }

        #region FSUIPC Read/Write Quick Access Methods

        // Get Byte[] from Token
        byte[] GetByteArray(int token, int length)
        {
            byte[] result = new byte[length];
            if (!h_FSUIPC.FSUIPC_Get(ref token, length, ref result))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Get<byte[]> Error");
                }));
                return null;
            }
            return result;
        }
        // Get Byte from Token
        byte GetByte(int token)
        {
            byte res = 0;
            if (!h_FSUIPC.FSUIPC_Get(ref token, ref res))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Get<byte> Error");
                }));
                return 0xff;
            }
            return res;
        }
        // Get Short from Token
        short GetShort(int token)
        {
            short res = 0;
            if (!h_FSUIPC.FSUIPC_Get(ref token, ref res))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Get<short> Error");
                }));
                return -1;
            }
            return res;
        }
        // Get Int from Token
        int GetInt(int token)
        {
            int res = 0;
            if (!h_FSUIPC.FSUIPC_Get(ref token, ref res))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Get<int> Error");
                }));
                return -1;
            }
            return res;
        }
        // Get Long from Token
        long GetLong(int token)
        {
            long res = 0;
            if (!h_FSUIPC.FSUIPC_Get(ref token, ref res))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Get<int> Error");
                }));
                return -1;
            }
            return res;
        }

        double GetDouble(int token)
        {
            byte[] data = GetByteArray(token, 8);
            return BitConverter.ToDouble(data, 0);
        }

        // Reads the value at the specified offset.
        void ReadValue<T>(int offset, int length, Action<T> callback)
        {
            int token = 0;
            int error = 0;
            T dwResult = default(T);

            if (!h_FSUIPC.FSUIPC_Read(offset, length, ref token, ref error))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Read<" + typeof(T).Name + "> Error " + error);
                }));
                return;
            }

            if (!h_FSUIPC.FSUIPC_Process(ref error))
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC_Process<" + typeof(T).Name + "> Error " + error);
                }));
                return;
            }

            if (typeof(T) == typeof(byte[]))
            {
                dwResult = (T)(object)GetByteArray(token, length);
            }
            else if (typeof(T) == typeof(string))
            {
                byte[] bytes = GetByteArray(token, length);
                if (bytes != null)
                {
                    dwResult = (T)(object)Encoding.UTF8.GetString(bytes).Replace("\0", "");
                }
            }
            else if (typeof(T) == typeof(byte))
            {
                dwResult = (T)(object)GetByte(token);
            }
            else if (typeof(T) == typeof(short))
            {
                dwResult = (T)(object)GetShort(token);
            }
            else if (typeof(T) == typeof(int))
            {
                dwResult = (T)(object)GetInt(token);
            }
            else if (typeof(T) == typeof(long))
            {
                dwResult = (T)(object)GetLong(token);
            }
            else if (typeof(T) == typeof(double))
            {
                dwResult = (T)(object)GetDouble(token);
            }
            else
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    MessageBox.Show("FSUIPC.ReadValue<" + typeof(T).Name + "> Error: unknown type.");
                }));
                dwResult = default(T);
            }

            if (callback != null)
            {
                callback(dwResult);
            }
        }

        public void ReadString(int offset, int length, Action<string> callback)
        {
            PendAction(new Action(() =>
            {
                ReadValue<string>(offset, length, callback);
            }));
        }

        public void ReadInt(int offset, Action<int> callback)
        {
            PendAction(new Action(() =>
            {
                ReadValue<int>(offset, 4, callback);
            }));
        }

        public void ReadByte(int offset, Action<byte> callback)
        {
            PendAction(new Action(() =>
            {
                ReadValue<byte>(offset, 1, callback);
            }));
        }

        public void ReadShort(int offset, Action<short> callback)
        {
            PendAction(new Action(() =>
            {
                ReadValue<short>(offset, 2, callback);
            }));
        }

        public void ReadDouble(int offset, Action<double> callback)
        {
            PendAction(new Action(() =>
                {
                    ReadValue<double>(offset, 8, callback);
                }));
        }

        public void ReadLong(int offset, Action<long> callback)
        {
            PendAction(new Action(() =>
                {
                    ReadValue<long>(offset, 8, callback);
                }));
        }

        #endregion

    }
}
